package org.liaohailong.library.widget.photoview;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.liaohailong.library.widget.photoview.guesture.VGestureDetector;

/**
 * create by liaohailong
 * time 2023/1/9 10:57
 * desc: 支持手势操作的ImageView
 * <p>
 * <p>
 * <p>
 * 3 x 3 矩阵
 * <p>
 * [MSCALE_X, MSKEW_X, MTRANS_X]
 * [MSKEW_Y, MSCALE_Y, MTRANS_Y]
 * [MPERSP_0, MPERSP_1, MPERSP_2]
 * <p>
 * [cosθ, -sinθ, dx]
 * [sinθ, cosθ, dy]
 * [0, 0, 1]
 */
public class VPhotoView extends androidx.appcompat.widget.AppCompatImageView implements
        VGestureDetector.Callback {

    private void log(@NonNull String format, Object... args) {
        String msg = String.format(format, args);
        Log.i("PhotoView", msg);
    }

    /**
     * 旋转手势监听
     */
    private final VGestureDetector gestureDetector;


    /**
     * 借助一个bitmap来绘制原本 ImageView 的内容
     * 好处：节约了仿写 ImageView 中所有辅助性的逻辑，比如 scaleType adjustViewBounds 等等
     * 坏处：多出一块跟View屏幕尺寸一样大小的bitmap占用内存，需要注意释放资源，onAttached onDetached 及时释放资源
     */
    private Bitmap contentBitmap;
    private Canvas contentCanvas;
    private boolean drawContent; // 内容改变，尺寸改变，重新渲染一次
    private Paint contentPaint;


    /**
     * 手势交互相关
     */
    private final Matrix scrollMatrix;
    private final Matrix scaleMatrix;
    private final Matrix rotateMatrix;
    private final Matrix totalMatrix;

    private float currScale = 1.0f;


    /**
     * 矫正动画相关
     */
    private int totalRotateDeg;
    private float lastRotateCx; // 上次旋转的中心点，x坐标
    private float lastRotateCy; // 上次旋转的中心点，y坐标

    private boolean isAdjustingRotate;
    private ValueAnimator rotateAdjustAnim;


    public VPhotoView(Context context) {
        this(context, null);
    }

    public VPhotoView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VPhotoView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        // 初始化手势监听
        gestureDetector = new VGestureDetector(context);
        gestureDetector.setCallback(this);

        // 操作矩阵，记录用户手势操作信息
        scrollMatrix = new Matrix();
        scaleMatrix = new Matrix();
        rotateMatrix = new Matrix();
        totalMatrix = new Matrix();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        release();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        prepareIfNeed();
    }

    /**
     * 准备一下装载 ImageView 绘制内容的对象，主要是Bitmap和Canvas
     */
    private void prepareIfNeed() {
        int w = getMeasuredWidth();
        int h = getMeasuredHeight();
        // 可能未执行测量流程，或者本视图不可见
        if (w <= 0 || h <= 0) return;

        if (contentBitmap == null) {
            contentBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            contentCanvas = new Canvas(contentBitmap);
            drawContent = true;
        } else if (contentBitmap.getWidth() != w || contentBitmap.getHeight() != h) {
            contentBitmap.recycle();
            contentBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            contentCanvas = new Canvas(contentBitmap);
            drawContent = true;
        }

        if (contentPaint == null) {
            contentPaint = new Paint();
            contentPaint.setAntiAlias(true);
            contentPaint.setDither(true);
            contentPaint.setFilterBitmap(true);
        }
    }

    /**
     * 从父容器移除的话，就等于不需要看了，此时最好释放bitmap资源，腾出空间
     */
    private void release() {
        if (contentBitmap != null) {
            contentBitmap.recycle();
            contentBitmap = null;
            contentCanvas = null;
        }
    }

    @Override
    public void setImageDrawable(@Nullable Drawable drawable) {
        super.setImageDrawable(drawable);
        prepareIfNeed();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        gestureDetector.onTouchEvent(event);
        return true;
    }

    private final float[] values = new float[3 * 3];

    @Override
    protected void onDraw(Canvas canvas) {
        prepareIfNeed();
        if (contentBitmap == null || contentCanvas == null) {
            super.onDraw(canvas);
            return;
        }

        // 绘制内容图片，防止重复绘制
        if (drawContent) {
            contentBitmap.eraseColor(Color.TRANSPARENT);
            super.onDraw(contentCanvas);
            drawContent = false;
        }


        // 处理手势
        totalMatrix.postConcat(rotateMatrix);
        totalMatrix.postConcat(scaleMatrix);
        totalMatrix.postConcat(scrollMatrix);


        totalMatrix.getValues(values);
        float tan = values[3] / values[0];
        int totalDeg = (int) Math.round(Math.toDegrees(Math.atan(tan)));
        log("totalRotateDeg: %s", totalRotateDeg);
        log("totalDeg: %s", totalDeg);

        log("currScale: %s", currScale);
        log("scaleX: %s, scaleY: %s", values[0], values[4]);

        /*
         * 总结：
         * 1，旋转矩阵会影响缩放值
         * 2，旋转/缩放 中心锚点处理，先反平移到原点，操作完成后再移动回来
         * 3，平移量受到旋转和平移双重影响
         *
         * 通过Matrix还原rotateDeg 比较简单使用 tanθ = sinθ/cosθ 再用tanθ求出角度，存在4象限特点，-角度问题
         * 通过Matrix还原scale值?
         * 通过Matrix还原translate值？
         *
         * 还原动画：restore()
         *
         * */


        /*
         * 这里的矩阵是一次性内容，基于上次ACTION_MOVE计算出来的，
         * 所以绘制完一次之后就需要清空，防止重复被totalMatrix使用
         * */
        scaleMatrix.reset();
        rotateMatrix.reset();
        scrollMatrix.reset();

        // 将ImageView本身的内容，通过自己处理的手势来渲染
        canvas.drawBitmap(contentBitmap, totalMatrix, contentPaint);
    }


    @Override
    public void onActonDown(@NonNull MotionEvent event) {
        if (isAdjusting()) {
            log("onActonDown isAdjusting");
            cancelAdjustPos();
        }
    }

    @Override
    public void onScroll(float dx, float dy) {
//        log("onScroll dx: %s, dy: %s", dx, dy);
        scrollMatrix.setTranslate(-dx, -dy);
        invalidate();
    }

    @Override
    public void onScale(float scale, float cx, float cy) {
//        log("onScale scale: %s, cx: %s, cy: %s", scale, cx, cy);
        currScale *= scale;
        scaleMatrix.setScale(scale, scale, cx, cy);
        invalidate();
    }

    @Override
    public void onRotate(int rotateDeg, float cx, float cy) {
//        log("onRotate rotateDeg: %s, cx: %s, cy: %s", rotateDeg, cx, cy);
        rotateMatrix.setRotate(rotateDeg, cx, cy);
        lastRotateCx = cx;
        lastRotateCy = cy;
        totalRotateDeg += rotateDeg;
        invalidate();
    }

    @Override
    public void onActionUp(@NonNull MotionEvent event) {
        // 用户松手
//        adjustPosition();
    }

    private void adjustPosition() {
        // 矫正旋转
        int rotateOffset = totalRotateDeg % 90;
        if (rotateOffset != 0) {
            // 当图片旋转操作之后，不是90度的倍数，表示图片看起来有点歪，此时矫正到90度的倍数
            int toDegrees;
            if (rotateOffset < 0) {
                if (rotateOffset > -45) {
                    toDegrees = -rotateOffset;
                } else {
                    toDegrees = -(90 + rotateOffset);
                }
            } else {
                if (rotateOffset < 45) {
                    toDegrees = -rotateOffset;
                } else {
                    toDegrees = 90 - rotateOffset;
                }
            }

            final int toDeg = toDegrees;

            log("adjustPosition rotateOffset: %s", rotateOffset);
            log("adjustPosition toDegrees: %s", toDegrees);

            // 旋转1度，耗时5ms
            int rotateAnimDurationMs = Math.abs(toDeg) * 5;

            rotateAdjustAnim = ValueAnimator.ofFloat(0.0f, 1.0f);
            rotateAdjustAnim.setDuration(rotateAnimDurationMs);
            rotateAdjustAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

                private int lastDeg = 0;

                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    float progress = (float) animation.getAnimatedValue();
                    int currentDeg = (int) (toDeg * progress);
                    int rotateDeg = currentDeg - lastDeg;
                    onRotate(rotateDeg, lastRotateCx, lastRotateCy);
                    lastDeg = currentDeg;
                }
            });
            rotateAdjustAnim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {
                    super.onAnimationStart(animation);
                    isAdjustingRotate = true;
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    isAdjustingRotate = false;

                }
            });
            rotateAdjustAnim.start();
        }

    }

    /**
     * @return true表示正在执行矫正操作
     */
    private boolean isAdjusting() {
        return isAdjustingRotate;
    }

    /**
     * 取消矫正操作：旋转，平移
     */
    private void cancelAdjustPos() {
        // 停止旋转动画矫正
        if (rotateAdjustAnim != null) {
            rotateAdjustAnim.cancel();
            rotateAdjustAnim = null;
        }
    }


    public void restore() {
        Matrix src = new Matrix(); // 当前操作矩阵
        src.set(totalMatrix);

        Matrix dst = new Matrix(); // 还原操作矩阵

        final float[] srcValues = new float[3 * 3];
        final float[] dstValues = new float[3 * 3];
        src.getValues(srcValues);
        dst.getValues(dstValues);

        // 还原属性
        startRestoreAnim(srcValues, dstValues, 0); // scaleX
        startRestoreAnim(srcValues, dstValues, 1); // skewX
        startRestoreAnim(srcValues, dstValues, 2); // transX
        startRestoreAnim(srcValues, dstValues, 3); // scaleY
        startRestoreAnim(srcValues, dstValues, 4); // skewY
        startRestoreAnim(srcValues, dstValues, 5); // transY
    }


    private void startRestoreAnim(float[] srcValues, float[] dstValues, int index) {
        ValueAnimator anim0 = ValueAnimator.ofFloat(srcValues[index], dstValues[index]);
        anim0.setDuration(500);
        anim0.addUpdateListener(animation -> {
            float scaleX = (float) animation.getAnimatedValue();
            srcValues[index] = scaleX;
            totalMatrix.setValues(srcValues);
            invalidate();
        });
        anim0.start();
    }
}
